

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=auto>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/blogshow/img/favicon.png">
  <link rel="icon" href="/blogshow/img/favicon.png">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
    <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="description" content="">
  <meta name="author" content="John Doe">
  <meta name="keywords" content="">
  <meta name="description" content="Netty学习之NIO基础本博客是根据黑马程序员Netty实战学习时所做的笔记 一、三大组件简介1.Channel与BufferJava NIO系统的核心在于：通道(Channel)和缓冲区(Buffer).通道表是打开到IO设备的连接(例如：文件、套接字)。若需要使用 NIO 系统，需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区，对数据进行处理 简而言之，通道负责传输">
<meta property="og:type" content="article">
<meta property="og:title" content="Netty学习之NIO基础">
<meta property="og:url" content="https://gitee.com/jiwentao/blogshow/2020/10/25/Netty%E5%AD%A6%E4%B9%A0%E4%B9%8BNIO%E5%9F%BA%E7%A1%80/index.html">
<meta property="og:site_name" content="啊泽show的博客">
<meta property="og:description" content="Netty学习之NIO基础本博客是根据黑马程序员Netty实战学习时所做的笔记 一、三大组件简介1.Channel与BufferJava NIO系统的核心在于：通道(Channel)和缓冲区(Buffer).通道表是打开到IO设备的连接(例如：文件、套接字)。若需要使用 NIO 系统，需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区，对数据进行处理 简而言之，通道负责传输">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gitee.com/blogshow/.com//netty/20210418181918.png">
<meta property="og:image" content="https://gitee.com/blogshow/.com//netty/20210418181933.png">
<meta property="og:image" content="https://gitee.com/blogshow/.com//netty/20210418181947.png">
<meta property="og:image" content="https://gitee.com/blogshow/.com//netty/0021.png">
<meta property="og:image" content="https://gitee.com/jiwentao/blogshow/blob/master/img/netty/0018.png">
<meta property="og:image" content="https://gitee.com/blogshow/.com//netty/0019.png">
<meta property="og:image" content="https://gitee.com/blogshow/.com//netty/0020.png">
<meta property="og:image" content="https://gitee.com/blogshow/.com//netty/0021.png">
<meta property="og:image" content="https://gitee.com/blogshow/.com//netty/0022.png">
<meta property="og:image" content="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210414192429.png">
<meta property="og:image" content="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210414193143.png">
<meta property="og:image" content="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210415103442.png">
<meta property="og:image" content="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210418151243.png">
<meta property="og:image" content="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210418151605.png">
<meta property="og:image" content="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210418152137.png">
<meta property="og:image" content="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210418154208.png">
<meta property="og:image" content="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210418160106.png">
<meta property="article:published_time" content="2020-10-25T02:00:00.000Z">
<meta property="article:modified_time" content="2021-10-28T03:35:23.961Z">
<meta property="article:author" content="John Doe">
<meta property="article:tag" content="java">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://gitee.com/blogshow/.com//netty/20210418181918.png">
  
  <title>Netty学习之NIO基础 - 啊泽show的博客</title>

  <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4/dist/css/bootstrap.min.css" />


  <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@4/github-markdown.min.css" />
  <link  rel="stylesheet" href="/blogshow/lib/hint/hint.min.css" />

  
    
    
      
      <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@10/styles/github-gist.min.css" />
    
  

  
    <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3/dist/jquery.fancybox.min.css" />
  


<!-- 主题依赖的图标库，不要自行修改 -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_ba1fz6golrf.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_kmeydafke9r.css">


<link  rel="stylesheet" href="/blogshow/css/main.css" />

<!-- 自定义样式保持在最底部 -->


  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    var CONFIG = {"hostname":"gitee.com","root":"/blogshow/","version":"1.8.12","typing":{"enable":true,"typeSpeed":70,"cursorChar":"_","loop":false},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"right","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"copy_btn":true,"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":true,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":false,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null,"path":"window.location.pathname"}},"search_path":"/blogshow/local-search.xml"};
  </script>
  <script  src="/blogshow/js/utils.js" ></script>
  <script  src="/blogshow/js/color-schema.js" ></script>
<meta name="generator" content="Hexo 5.4.0"></head>


<body>
  <header style="height: 70vh;">
    <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/blogshow/">
      <strong>啊泽show</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blogshow/">
                <i class="iconfont icon-home-fill"></i>
                首页
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blogshow/archives/">
                <i class="iconfont icon-archive-fill"></i>
                归档
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blogshow/categories/">
                <i class="iconfont icon-category-fill"></i>
                分类
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blogshow/tags/">
                <i class="iconfont icon-tags-fill"></i>
                标签
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blogshow/about/">
                <i class="iconfont icon-user-fill"></i>
                关于
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blogshow/links/">
                <i class="iconfont icon-link-fill"></i>
                友链
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item dropdown">
              <a class="nav-link dropdown-toggle" target="_self" href="javascript:;" role="button"
                 data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                <i class="iconfont icon-books"></i>
                文档
              </a>
              <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                
                  
                  
                  
                  <a class="dropdown-item" target="_blank" rel="noopener" href="https://hexo.fluid-dev.com/">
                    
                    主题博客
                  </a>
                
                  
                  
                  
                  <a class="dropdown-item" target="_blank" rel="noopener" href="https://hexo.fluid-dev.com/docs/guide/">
                    
                    配置指南
                  </a>
                
                  
                  
                  
                  <a class="dropdown-item" target="_blank" rel="noopener" href="https://hexo.fluid-dev.com/docs/icon/">
                    
                    图标用法
                  </a>
                
              </div>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              &nbsp;<i class="iconfont icon-search"></i>&nbsp;
            </a>
          </li>
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">&nbsp;<i
                class="iconfont icon-dark" id="color-toggle-icon"></i>&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

    <div class="banner" id="banner" parallax=true
         style="background: url('/blogshow/img/index_banner.png') no-repeat center center;
           background-size: cover;">
      <div class="full-bg-img">
        <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
          <div class="page-header text-center fade-in-up">
            <span class="h2" id="subtitle" title="Netty学习之NIO基础">
              
            </span>

            
              <div class="mt-3">
  
  
    <span class="post-meta">
      <i class="iconfont icon-date-fill" aria-hidden="true"></i>
      <time datetime="2020-10-25 10:00" pubdate>
        2020年10月25日 上午
      </time>
    </span>
  
</div>

<div class="mt-1">
  
    <span class="post-meta mr-2">
      <i class="iconfont icon-chart"></i>
      37k 字
    </span>
  

  
    <span class="post-meta mr-2">
      <i class="iconfont icon-clock-fill"></i>
      
      
      116 分钟
    </span>
  

  
  
    
      <!-- 不蒜子统计文章PV -->
      <span id="busuanzi_container_page_pv" style="display: none">
        <i class="iconfont icon-eye" aria-hidden="true"></i>
        <span id="busuanzi_value_page_pv"></span> 次
      </span>
    
  
</div>

            
          </div>

          
        </div>
      </div>
    </div>
  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="d-none d-lg-block col-lg-2"></div>
    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div class="py-5" id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">Netty学习之NIO基础</h1>
            
            <div class="markdown-body">
              <h1 id="Netty学习之NIO基础"><a href="#Netty学习之NIO基础" class="headerlink" title="Netty学习之NIO基础"></a>Netty学习之NIO基础</h1><p>本博客是根据<a target="_blank" rel="noopener" href="https://www.bilibili.com/video/BV1py4y1E7oA?p=2">黑马程序员Netty实战</a>学习时所做的笔记</p>
<h2 id="一、三大组件简介"><a href="#一、三大组件简介" class="headerlink" title="一、三大组件简介"></a>一、三大组件简介</h2><h3 id="1-Channel与Buffer"><a href="#1-Channel与Buffer" class="headerlink" title="1.Channel与Buffer"></a>1.Channel与Buffer</h3><p>Java NIO系统的核心在于：通道(Channel)和缓冲区(Buffer).通道表是打开到IO设备的连接(例如：文件、套接字)。若需要使用 NIO 系统，需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区，对数据进行处理</p>
<p>简而言之，<strong>通道负责传输，缓冲区负责存储</strong></p>
<p>常见的Channel有以下四种，其中FileChannel主要用于文件传输，其余三种用于网络通信</p>
<ul>
<li>FileChannel</li>
<li>DatagramChannel</li>
<li>SocketChannel</li>
<li>ServerSocketChannel</li>
</ul>
<p>Buffer有以下几种，其中使用较多的是ByteBuffer</p>
<ul>
<li>ByteBuffer<ul>
<li>MappedByteBuffer</li>
<li>DirectByteBuffer</li>
<li>HeapByteBuffer</li>
</ul>
</li>
<li>ShortBuffer</li>
<li>IntBuffer</li>
<li>LongBuffer</li>
<li>FloatBuffer</li>
<li>DoubleBuffer</li>
<li>CharBuffer</li>
</ul>
<h3 id="2-Selector"><a href="#2-Selector" class="headerlink" title="2.Selector"></a>2.Selector</h3><p>在使用Selector之前，处理socket连接还有以下两种方法</p>
<p><strong>使用多线程技术</strong>,为每个连接分别开辟一个线程，分别去处理对应的socke连接<br><img src="/blogshow/.com//netty/20210418181918.png" srcset="/blogshow/img/loading.gif" lazyload><br>这种方法存在以下几个问题</p>
<ul>
<li><p>内存占用高</p>
<ul>
<li>每个线程都需要占用一定的内存，当连接较多时，会开辟大量线程，导致占用大量内存</li>
</ul>
</li>
<li><p>线程上下文切换成本高</p>
</li>
<li><p>只适合连接数少的场景</p>
<ul>
<li>连接数过多，会导致创建很多线程，从而出现问题</li>
</ul>
<p>  <strong>使用线程池技术</strong>,使用线程池，让线程池中的线程去处理连接<br>  <img src="/blogshow/.com//netty/20210418181933.png" srcset="/blogshow/img/loading.gif" lazyload><br>  这种方法存在以下几个问题</p>
</li>
<li><p>阻塞模式下，线程仅能处理一个连接</p>
<ul>
<li>线程池中的线程获取任务（task）后，只有当其执行完任务之后（断开连接后），才会去获取并执行下一个任务</li>
<li>若socke连接一直未断开，则其对应的线程无法处理其他socke连接</li>
</ul>
</li>
<li><p>仅适合短连接场景</p>
<ul>
<li>短连接即建立连接发送请求并响应后就立即断开，使得线程池中的线程可以快速处理其他连接</li>
</ul>
</li>
</ul>
<p><strong>使用选择器selector</strong><br>selector 的作用就是配合一个线程来管理多个 channel（<strong>fileChannel因为是阻塞式的，所以无法使用selector</strong>），获取这些 channel 上发生的事件，这些 channel 工作在非阻塞模式下，当一个channel中没有执行任务时，可以去执行其他channel中的任务。<strong>适合连接数多，但流量较少的场景</strong><br><img src="/blogshow/.com//netty/20210418181947.png" srcset="/blogshow/img/loading.gif" lazyload><br>若事件未就绪，调用 selector 的 select() 方法会阻塞线程，直到 channel 发生了就绪事件。这些事件就绪后，select 方法就会返回这些事件交给 thread 来处理</p>
<h2 id="二、ByteBuffer"><a href="#二、ByteBuffer" class="headerlink" title="二、ByteBuffer"></a>二、ByteBuffer</h2><p><strong>使用案例一：</strong>使用 ByteBuffer 来读取文件内容</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestByteBuffer</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>        <span class="hljs-comment">// 获得FileChannel</span><br>        <span class="hljs-keyword">try</span> (FileChannel channel = <span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">&quot;data.txt&quot;</span>).getChannel()) &#123;<br>            <span class="hljs-comment">// 获得缓冲区</span><br>            ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">10</span>);<br>            <span class="hljs-keyword">int</span> hasNext = <span class="hljs-number">0</span>;<br>            StringBuilder builder = <span class="hljs-keyword">new</span> StringBuilder();<br>            <span class="hljs-keyword">while</span>((hasNext = channel.read(buffer)) &gt; <span class="hljs-number">0</span>) &#123;<br>                <span class="hljs-comment">// 切换读模式 limit=position, position=0</span><br>                buffer.flip();<br>                <span class="hljs-comment">// 当buffer中还有数据时，获取其中的数据</span><br>                <span class="hljs-keyword">while</span>(buffer.hasRemaining()) &#123;<br>                    builder.append((<span class="hljs-keyword">char</span>)buffer.get());<br>                &#125;<br>                <span class="hljs-comment">// 切换写模式 position=0, limit=capacity</span><br>                buffer.clear();<br>            &#125;<br>            System.out.println(builder.toString());<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<h3 id="1-ByteBuffer-正确使用姿势"><a href="#1-ByteBuffer-正确使用姿势" class="headerlink" title="1. ByteBuffer 正确使用姿势"></a>1. ByteBuffer 正确使用姿势</h3><ul>
<li>向 buffer 写入数据，例如调用 channel.read(buffer)</li>
<li>调用 flip() 切换至读模式<ul>
<li>flip会使得buffer中的limit变为position，position变为0</li>
</ul>
</li>
<li>从 buffer 读取数据，例如调用 buffer.get()</li>
<li>调用 clear() 或者compact()切换至写模式<ul>
<li>调用clear()方法时position=0，limit变为capacity</li>
<li>调用compact()方法时，会将缓冲区中的未读数据压缩到缓冲区前面</li>
</ul>
</li>
<li>重复以上步骤<h3 id="2-ByteBuffer-结构"><a href="#2-ByteBuffer-结构" class="headerlink" title="2.ByteBuffer 结构"></a>2.ByteBuffer 结构</h3>ByteBuffer 有以下重要属性</li>
</ul>
<ul>
<li><p>capacity 缓冲区的容量。通过构造函数赋予，一旦设置，无法更改</p>
</li>
<li><p>position 下一个读写位置的索引（类似PC）。缓冲区的位置不能为负，并且不能大于limit</p>
</li>
<li><p>limit 缓冲区的界限。位于limit 后的数据不可读写。缓冲区的限制不能为负，并且不能大于其容量<br>一开始</p>
</li>
</ul>
<p> <img src="/blogshow/.com//netty/0021.png" srcset="/blogshow/img/loading.gif" lazyload></p>
<p>写模式下，position 是写入位置，limit 等于容量，下图表示写入了 4 个字节后的状态</p>
<p><img src="https://gitee.com/jiwentao/blogshow/blob/master/img/netty/0018.png" srcset="/blogshow/img/loading.gif" lazyload alt="img/netty/0018.png · wonderful/blogshow - Gitee.com"></p>
<p>flip 动作发生后，position 切换为读取位置，limit 切换为读取限制</p>
<p><img src="/blogshow/.com//netty/0019.png" srcset="/blogshow/img/loading.gif" lazyload></p>
<p>读取 4 个字节后，状态</p>
<p><img src="/blogshow/.com//netty/0020.png" srcset="/blogshow/img/loading.gif" lazyload></p>
<p>clear 动作发生后，状态</p>
<p><img src="/blogshow/.com//netty/0021.png" srcset="/blogshow/img/loading.gif" lazyload></p>
<p>compact 方法，是把未读完的部分向前压缩，然后切换至写模式</p>
<p><img src="/blogshow/.com//netty/0022.png" srcset="/blogshow/img/loading.gif" lazyload></p>
<h3 id="3-ByteBuffer方法调用及演示"><a href="#3-ByteBuffer方法调用及演示" class="headerlink" title="3.ByteBuffer方法调用及演示"></a>3.ByteBuffer方法调用及演示</h3><p><strong>ByteBuffer调试工具类</strong><br>需要先导入netty依赖</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>io.netty<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>netty-all<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>4.1.51.Final<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.nio.ByteBuffer;<br><br><span class="hljs-keyword">import</span> io.netty.util.internal.MathUtil;<br><span class="hljs-keyword">import</span> io.netty.util.internal.StringUtil;<br><span class="hljs-keyword">import</span> io.netty.util.internal.MathUtil.*;<br><br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> Jwt</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@date</span> 2021/10/12 15:59</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ByteBufferUtil</span> </span>&#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">char</span>[] BYTE2CHAR = <span class="hljs-keyword">new</span> <span class="hljs-keyword">char</span>[<span class="hljs-number">256</span>];<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">char</span>[] HEXDUMP_TABLE = <span class="hljs-keyword">new</span> <span class="hljs-keyword">char</span>[<span class="hljs-number">256</span> * <span class="hljs-number">4</span>];<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String[] HEXPADDING = <span class="hljs-keyword">new</span> String[<span class="hljs-number">16</span>];<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String[] HEXDUMP_ROWPREFIXES = <span class="hljs-keyword">new</span> String[<span class="hljs-number">65536</span> &gt;&gt;&gt; <span class="hljs-number">4</span>];<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String[] BYTE2HEX = <span class="hljs-keyword">new</span> String[<span class="hljs-number">256</span>];<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String[] BYTEPADDING = <span class="hljs-keyword">new</span> String[<span class="hljs-number">16</span>];<br><br>    <span class="hljs-keyword">static</span> &#123;<br>        <span class="hljs-keyword">final</span> <span class="hljs-keyword">char</span>[] DIGITS = <span class="hljs-string">&quot;0123456789abcdef&quot;</span>.toCharArray();<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">256</span>; i++) &#123;<br>            HEXDUMP_TABLE[i &lt;&lt; <span class="hljs-number">1</span>] = DIGITS[i &gt;&gt;&gt; <span class="hljs-number">4</span> &amp; <span class="hljs-number">0x0F</span>];<br>            HEXDUMP_TABLE[(i &lt;&lt; <span class="hljs-number">1</span>) + <span class="hljs-number">1</span>] = DIGITS[i &amp; <span class="hljs-number">0x0F</span>];<br>        &#125;<br><br>        <span class="hljs-keyword">int</span> i;<br><br>        <span class="hljs-comment">// Generate the lookup table for hex dump paddings</span><br>        <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; HEXPADDING.length; i++) &#123;<br>            <span class="hljs-keyword">int</span> padding = HEXPADDING.length - i;<br>            StringBuilder buf = <span class="hljs-keyword">new</span> StringBuilder(padding * <span class="hljs-number">3</span>);<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; padding; j++) &#123;<br>                buf.append(<span class="hljs-string">&quot;   &quot;</span>);<br>            &#125;<br>            HEXPADDING[i] = buf.toString();<br>        &#125;<br><br>        <span class="hljs-comment">// Generate the lookup table for the start-offset header in each row (up to 64KiB).</span><br>        <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; HEXDUMP_ROWPREFIXES.length; i++) &#123;<br>            StringBuilder buf = <span class="hljs-keyword">new</span> StringBuilder(<span class="hljs-number">12</span>);<br>            buf.append(StringUtil.NEWLINE);<br>            buf.append(Long.toHexString(i &lt;&lt; <span class="hljs-number">4</span> &amp; <span class="hljs-number">0xFFFFFFFFL</span> | <span class="hljs-number">0x100000000L</span>));<br>            buf.setCharAt(buf.length() - <span class="hljs-number">9</span>, <span class="hljs-string">&#x27;|&#x27;</span>);<br>            buf.append(<span class="hljs-string">&#x27;|&#x27;</span>);<br>            HEXDUMP_ROWPREFIXES[i] = buf.toString();<br>        &#125;<br><br>        <span class="hljs-comment">// Generate the lookup table for byte-to-hex-dump conversion</span><br>        <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; BYTE2HEX.length; i++) &#123;<br>            BYTE2HEX[i] = <span class="hljs-string">&#x27; &#x27;</span> + StringUtil.byteToHexStringPadded(i);<br>        &#125;<br><br>        <span class="hljs-comment">// Generate the lookup table for byte dump paddings</span><br>        <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; BYTEPADDING.length; i++) &#123;<br>            <span class="hljs-keyword">int</span> padding = BYTEPADDING.length - i;<br>            StringBuilder buf = <span class="hljs-keyword">new</span> StringBuilder(padding);<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; padding; j++) &#123;<br>                buf.append(<span class="hljs-string">&#x27; &#x27;</span>);<br>            &#125;<br>            BYTEPADDING[i] = buf.toString();<br>        &#125;<br><br>        <span class="hljs-comment">// Generate the lookup table for byte-to-char conversion</span><br>        <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; BYTE2CHAR.length; i++) &#123;<br>            <span class="hljs-keyword">if</span> (i &lt;= <span class="hljs-number">0x1f</span> || i &gt;= <span class="hljs-number">0x7f</span>) &#123;<br>                BYTE2CHAR[i] = <span class="hljs-string">&#x27;.&#x27;</span>;<br>            &#125; <span class="hljs-keyword">else</span> &#123;<br>                BYTE2CHAR[i] = (<span class="hljs-keyword">char</span>) i;<br>            &#125;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 打印所有内容</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> buffer</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">debugAll</span><span class="hljs-params">(ByteBuffer buffer)</span> </span>&#123;<br>        <span class="hljs-keyword">int</span> oldlimit = buffer.limit();<br>        buffer.limit(buffer.capacity());<br>        StringBuilder origin = <span class="hljs-keyword">new</span> StringBuilder(<span class="hljs-number">256</span>);<br>        appendPrettyHexDump(origin, buffer, <span class="hljs-number">0</span>, buffer.capacity());<br>        System.out.println(<span class="hljs-string">&quot;+--------+-------------------- all ------------------------+----------------+&quot;</span>);<br>        System.out.printf(<span class="hljs-string">&quot;position: [%d], limit: [%d]\n&quot;</span>, buffer.position(), oldlimit);<br>        System.out.println(origin);<br>        buffer.limit(oldlimit);<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 打印可读取内容</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> buffer</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">debugRead</span><span class="hljs-params">(ByteBuffer buffer)</span> </span>&#123;<br>        StringBuilder builder = <span class="hljs-keyword">new</span> StringBuilder(<span class="hljs-number">256</span>);<br>        appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position());<br>        System.out.println(<span class="hljs-string">&quot;+--------+-------------------- read -----------------------+----------------+&quot;</span>);<br>        System.out.printf(<span class="hljs-string">&quot;position: [%d], limit: [%d]\n&quot;</span>, buffer.position(), buffer.limit());<br>        System.out.println(builder);<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">appendPrettyHexDump</span><span class="hljs-params">(StringBuilder dump, ByteBuffer buf, <span class="hljs-keyword">int</span> offset, <span class="hljs-keyword">int</span> length)</span> </span>&#123;<br>        <span class="hljs-keyword">if</span> (MathUtil.isOutOfBounds(offset, length, buf.capacity())) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IndexOutOfBoundsException(<br>                    <span class="hljs-string">&quot;expected: &quot;</span> + <span class="hljs-string">&quot;0 &lt;= offset(&quot;</span> + offset + <span class="hljs-string">&quot;) &lt;= offset + length(&quot;</span> + length<br>                            + <span class="hljs-string">&quot;) &lt;= &quot;</span> + <span class="hljs-string">&quot;buf.capacity(&quot;</span> + buf.capacity() + <span class="hljs-string">&#x27;)&#x27;</span>);<br>        &#125;<br>        <span class="hljs-keyword">if</span> (length == <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br>        dump.append(<br>                <span class="hljs-string">&quot;         +-------------------------------------------------+&quot;</span> +<br>                        StringUtil.NEWLINE + <span class="hljs-string">&quot;         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |&quot;</span> +<br>                        StringUtil.NEWLINE + <span class="hljs-string">&quot;+--------+-------------------------------------------------+----------------+&quot;</span>);<br><br>        <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> startIndex = offset;<br>        <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> fullRows = length &gt;&gt;&gt; <span class="hljs-number">4</span>;<br>        <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> remainder = length &amp; <span class="hljs-number">0xF</span>;<br><br>        <span class="hljs-comment">// Dump the rows which have 16 bytes.</span><br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> row = <span class="hljs-number">0</span>; row &lt; fullRows; row++) &#123;<br>            <span class="hljs-keyword">int</span> rowStartIndex = (row &lt;&lt; <span class="hljs-number">4</span>) + startIndex;<br><br>            <span class="hljs-comment">// Per-row prefix.</span><br>            appendHexDumpRowPrefix(dump, row, rowStartIndex);<br><br>            <span class="hljs-comment">// Hex dump</span><br>            <span class="hljs-keyword">int</span> rowEndIndex = rowStartIndex + <span class="hljs-number">16</span>;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = rowStartIndex; j &lt; rowEndIndex; j++) &#123;<br>                dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);<br>            &#125;<br>            dump.append(<span class="hljs-string">&quot; |&quot;</span>);<br><br>            <span class="hljs-comment">// ASCII dump</span><br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = rowStartIndex; j &lt; rowEndIndex; j++) &#123;<br>                dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);<br>            &#125;<br>            dump.append(<span class="hljs-string">&#x27;|&#x27;</span>);<br>        &#125;<br><br>        <span class="hljs-comment">// Dump the last row which has less than 16 bytes.</span><br>        <span class="hljs-keyword">if</span> (remainder != <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-keyword">int</span> rowStartIndex = (fullRows &lt;&lt; <span class="hljs-number">4</span>) + startIndex;<br>            appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);<br><br>            <span class="hljs-comment">// Hex dump</span><br>            <span class="hljs-keyword">int</span> rowEndIndex = rowStartIndex + remainder;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = rowStartIndex; j &lt; rowEndIndex; j++) &#123;<br>                dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);<br>            &#125;<br>            dump.append(HEXPADDING[remainder]);<br>            dump.append(<span class="hljs-string">&quot; |&quot;</span>);<br><br>            <span class="hljs-comment">// Ascii dump</span><br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = rowStartIndex; j &lt; rowEndIndex; j++) &#123;<br>                dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);<br>            &#125;<br>            dump.append(BYTEPADDING[remainder]);<br>            dump.append(<span class="hljs-string">&#x27;|&#x27;</span>);<br>        &#125;<br><br>        dump.append(StringUtil.NEWLINE +<br>                <span class="hljs-string">&quot;+--------+-------------------------------------------------+----------------+&quot;</span>);<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">appendHexDumpRowPrefix</span><span class="hljs-params">(StringBuilder dump, <span class="hljs-keyword">int</span> row, <span class="hljs-keyword">int</span> rowStartIndex)</span> </span>&#123;<br>        <span class="hljs-keyword">if</span> (row &lt; HEXDUMP_ROWPREFIXES.length) &#123;<br>            dump.append(HEXDUMP_ROWPREFIXES[row]);<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            dump.append(StringUtil.NEWLINE);<br>            dump.append(Long.toHexString(rowStartIndex &amp; <span class="hljs-number">0xFFFFFFFFL</span> | <span class="hljs-number">0x100000000L</span>));<br>            dump.setCharAt(dump.length() - <span class="hljs-number">9</span>, <span class="hljs-string">&#x27;|&#x27;</span>);<br>            dump.append(<span class="hljs-string">&#x27;|&#x27;</span>);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">short</span> <span class="hljs-title">getUnsignedByte</span><span class="hljs-params">(ByteBuffer buffer, <span class="hljs-keyword">int</span> index)</span> </span>&#123;<br>        <span class="hljs-keyword">return</span> (<span class="hljs-keyword">short</span>) (buffer.get(index) &amp; <span class="hljs-number">0xFF</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<p><strong>调用ByteBuffer的方法</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestByteBuffer</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>        ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">10</span>);<br>        <span class="hljs-comment">// 向buffer中写入1个字节的数据</span><br>        buffer.put((<span class="hljs-keyword">byte</span>)<span class="hljs-number">97</span>);<br>        <span class="hljs-comment">// 使用工具类，查看buffer状态</span><br>        ByteBufferUtil.debugAll(buffer);<br><br>        <span class="hljs-comment">// 向buffer中写入4个字节的数据</span><br>        buffer.put(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">98</span>, <span class="hljs-number">99</span>, <span class="hljs-number">100</span>, <span class="hljs-number">101</span>&#125;);<br>        ByteBufferUtil.debugAll(buffer);<br><br>        <span class="hljs-comment">// 获取数据</span><br>        buffer.flip();<br>        ByteBufferUtil.debugAll(buffer);<br>        System.out.println(buffer.get());<br>        System.out.println(buffer.get());<br>        ByteBufferUtil.debugAll(buffer);<br><br>        <span class="hljs-comment">// 使用compact切换模式</span><br>        buffer.compact();<br>        ByteBufferUtil.debugAll(buffer);<br><br>        <span class="hljs-comment">// 再次写入</span><br>        buffer.put((<span class="hljs-keyword">byte</span>)<span class="hljs-number">102</span>);<br>        buffer.put((<span class="hljs-keyword">byte</span>)<span class="hljs-number">103</span>);<br>        ByteBufferUtil.debugAll(buffer);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><strong>字符串与ByteBuffer的相互转换</strong><br><strong>方法一</strong><br>编码：字符串调用getByte方法获得byte数组，将byte数组放入ByteBuffer中<br>解码：先调用ByteBuffer的flip方法，然后通过StandardCharsets的decoder方法解码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Translate</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>        <span class="hljs-comment">// 准备两个字符串</span><br>        String str1 = <span class="hljs-string">&quot;hello&quot;</span>;<br>        String str2 = <span class="hljs-string">&quot;&quot;</span>;<br><br>        ByteBuffer buffer1 = ByteBuffer.allocate(<span class="hljs-number">16</span>);<br>        <span class="hljs-comment">// 通过字符串的getByte方法获得字节数组，放入缓冲区中</span><br>        buffer1.put(str1.getBytes());<br>        ByteBufferUtil.debugAll(buffer1);<br><br>        <span class="hljs-comment">// 将缓冲区中的数据转化为字符串</span><br>        <span class="hljs-comment">// 切换模式</span><br>        buffer1.flip();<br>        <br>        <span class="hljs-comment">// 通过StandardCharsets解码，获得CharBuffer，再通过toString获得字符串</span><br>        str2 = StandardCharsets.UTF_8.decode(buffer1).toString();<br>        System.out.println(str2);<br>        ByteBufferUtil.debugAll(buffer1);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<p><strong>方法二</strong><br>编码：字符串调用getByte()方法获得字节数组，将字节数组传给ByteBuffer的wrap()方法，通过该方法获得ByteBuffer。同样无需调用flip方法切换为读模式</p>
<p>解码：通过StandardCharsets的decoder方法解码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Translate</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>        <span class="hljs-comment">// 准备两个字符串</span><br>        String str1 = <span class="hljs-string">&quot;hello&quot;</span>;<br>        String str2 = <span class="hljs-string">&quot;&quot;</span>;<br><br>        <span class="hljs-comment">// 通过StandardCharsets的encode方法获得ByteBuffer</span><br>        <span class="hljs-comment">// 此时获得的ByteBuffer为读模式，无需通过flip切换模式</span><br>        ByteBuffer buffer1 = ByteBuffer.wrap(str1.getBytes());<br>        ByteBufferUtil.debugAll(buffer1);<br><br>        <span class="hljs-comment">// 将缓冲区中的数据转化为字符串</span><br>        <span class="hljs-comment">// 通过StandardCharsets解码，获得CharBuffer，再通过toString获得字符串</span><br>        str2 = StandardCharsets.UTF_8.decode(buffer1).toString();<br>        System.out.println(str2);<br>        ByteBufferUtil.debugAll(buffer1);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<h3 id="4-练习"><a href="#4-练习" class="headerlink" title="4.练习"></a>4.练习</h3><h4 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h4><p>网络上有多条数据发送给服务端，数据之间使用 \n 进行分隔<br>但由于某种原因这些数据在接收时，被进行了重新组合，例如原始数据有3条为</p>
<ul>
<li>Hello,world\n</li>
<li>I’m Nyima\n</li>
<li>How are you?\n</li>
</ul>
<p>变成了下面的两个 byteBuffer (粘包，半包)</p>
<ul>
<li>Hello,world\nI’m Nyima\nHo</li>
<li>w are you?\n</li>
</ul>
<h4 id="出现原因"><a href="#出现原因" class="headerlink" title="出现原因"></a>出现原因</h4><p><strong>粘包</strong></p>
<p>发送方在发送数据时，并不是一条一条地发送数据，而是<strong>将数据整合在一起</strong>，当数据达到一定的数量后再一起发送。这就会导致多条信息被放在一个缓冲区中被一起发送出去</p>
<p><strong>半包</strong></p>
<p>接收方的缓冲区的大小是有限的，当接收方的缓冲区满了以后，就需要<strong>将信息截断</strong>，等缓冲区空了以后再继续放入数据。这就会发生一段完整的数据最后被截断的现象</p>
<h4 id="解决办法"><a href="#解决办法" class="headerlink" title="解决办法"></a>解决办法</h4><ul>
<li><p>通过get(index)方法遍历ByteBuffer，遇到分隔符时进行处理。</p>
<p>注意：get(index)不会改变position的值</p>
<ul>
<li>记录该段数据长度，以便于申请对应大小的缓冲区</li>
<li>将缓冲区的数据通过get()方法写入到target中</li>
</ul>
</li>
<li><p>调用<strong>compact方法</strong>切换模式，因为缓冲区中可能还有未读的数据</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs java">	<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ByteBufferDemo</span></span>&#123;<br>		<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span></span>&#123;<br>			 ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">32</span>);<br>			 <span class="hljs-comment">// 模拟粘包+半包</span><br>			buffer.put(<span class="hljs-string">&quot;Hello,world\nI&#x27;m Nyima\nHo&quot;</span>.getBytes());<br>			<span class="hljs-comment">// 调用split函数处理</span><br>      split(buffer);<br>      buffer.put(<span class="hljs-string">&quot;w are you?\n&quot;</span>.getBytes());<br>      split(buffer);<br>		&#125;<br>		<br>	 <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">split</span><span class="hljs-params">(ByteBuffer buffer)</span> </span>&#123;<br>        <span class="hljs-comment">// 切换为读模式</span><br>        buffer.flip();<br>        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; buffer.limit(); i++) &#123;<br>            <span class="hljs-comment">// 遍历寻找分隔符</span><br>            <span class="hljs-comment">// get(i)不会移动position</span><br>            <span class="hljs-keyword">if</span> (buffer.get(i) == <span class="hljs-string">&#x27;\n&#x27;</span>) &#123;<br>                <span class="hljs-comment">// 缓冲区长度</span><br>                <span class="hljs-keyword">int</span> length = i+<span class="hljs-number">1</span>-buffer.position();<br>                ByteBuffer target = ByteBuffer.allocate(length);<br>                <span class="hljs-comment">// 将前面的内容写入target缓冲区</span><br>                <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; length; j++) &#123;<br>                    <span class="hljs-comment">// 将buffer中的数据写入target中</span><br>                    target.put(buffer.get());<br>                &#125;<br>                <span class="hljs-comment">// 打印查看结果</span><br>                ByteBufferUtil.debugAll(target);<br>            &#125;<br>        &#125;<br>        <span class="hljs-comment">// 切换为写模式，但是缓冲区可能未读完，这里需要使用compact</span><br>        buffer.compact();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<p>运行结果<br>+——–+——————– all ————————+—————-+<br>position: [12], limit: [12]</p>
<pre><code>     +-------------------------------------------------+
     |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
</code></pre>
<p>+——–+————————————————-+—————-+<br>|00000000| 48 65 6c 6c 6f 2c 77 6f 72 6c 64 0a             |Hello,world.    |<br>+——–+————————————————-+—————-+<br>+——–+——————– all ————————+—————-+<br>position: [10], limit: [10]</p>
<pre><code>     +-------------------------------------------------+
     |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
</code></pre>
<p>+——–+————————————————-+—————-+<br>|00000000| 49 27 6d 20 4e 79 69 6d 61 0a                   |I’m Nyima.      |<br>+——–+————————————————-+—————-+<br>+——–+——————– all ————————+—————-+<br>position: [13], limit: [13]</p>
<pre><code>     +-------------------------------------------------+
     |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
</code></pre>
<p>+——–+————————————————-+—————-+<br>|00000000| 48 6f 77 20 61 72 65 20 79 6f 75 3f 0a          |How are you?.   |<br>+——–+————————————————-+—————-+</p>
</li>
</ul>
<h2 id="三、文件编程"><a href="#三、文件编程" class="headerlink" title="三、文件编程"></a>三、文件编程</h2><h3 id="1-FileChannel"><a href="#1-FileChannel" class="headerlink" title="1.FileChannel"></a>1.FileChannel</h3><h4 id="工作模式"><a href="#工作模式" class="headerlink" title="工作模式"></a>工作模式</h4><p>FileChannel只能在<strong>阻塞模式</strong>下工作，所以无法搭配Selector</p>
<h4 id="获取"><a href="#获取" class="headerlink" title="获取"></a>获取</h4><p>不能直接打开 FileChannel，必须通过 FileInputStream、FileOutputStream 或者 RandomAccessFile 来获取 FileChannel，它们都有 getChannel 方法</p>
<ul>
<li>通过 FileInputStream 获取的 channel <strong>只能读</strong></li>
<li>通过 FileOutputStream 获取的 channel <strong>只能写</strong></li>
<li>通过 RandomAccessFile 是否能读写<strong>根据构造 RandomAccessFile 时的读写模式决定</strong></li>
</ul>
<h4 id="读取"><a href="#读取" class="headerlink" title="读取"></a>读取</h4><p>通过 FileInputStream 获取channel，通过read方法将数据写入到ByteBuffer中<br>read方法的返回值表示读到了多少字节，若读到了文件末尾则返回-1</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> readBytes = channel.read(buffer);<br></code></pre></td></tr></table></figure>
<p><strong>可根据返回值判断是否读取完毕</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">while</span>(channel.read(buffer) &gt; <span class="hljs-number">0</span>) &#123;<br>    <span class="hljs-comment">// 进行对应操作</span><br>    ...<br>&#125;<br></code></pre></td></tr></table></figure>
<h4 id="写入"><a href="#写入" class="headerlink" title="写入"></a>写入</h4><p>因为channel也是有大小的，所以 write 方法并不能保证一次将 buffer 中的内容全部写入 channel。必须需要按照以下规则进行写入</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 通过hasRemaining()方法查看缓冲区中是否还有数据未写入到通道中</span><br><span class="hljs-keyword">while</span>(buffer.hasRemaining()) &#123;<br>	channel.write(buffer);<br>&#125;<br></code></pre></td></tr></table></figure>
<h4 id="关闭"><a href="#关闭" class="headerlink" title="关闭"></a>关闭</h4><p>通道需要close，一般情况通过try-with-resource进行关闭，最好使用以下方法获取strea以及channel，避免某些原因使得资源未被关闭</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestChannel</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;<br>        <span class="hljs-keyword">try</span> (FileInputStream fis = <span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">&quot;stu.txt&quot;</span>);<br>             FileOutputStream fos = <span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">&quot;student.txt&quot;</span>);<br>             FileChannel inputChannel = fis.getChannel();<br>             FileChannel outputChannel = fos.getChannel()) &#123;<br>            <br>            <span class="hljs-comment">// 执行对应操作</span><br>            ...<br>                <br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<h4 id="位置"><a href="#位置" class="headerlink" title="位置"></a>位置</h4><p><strong>position</strong><br>channel也拥有一个保存读取数据位置的属性，即position</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">long</span> pos = channel.position();<br></code></pre></td></tr></table></figure>
<p>可以通过position(int pos)设置channel中position的值</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">long</span> newPos = ...;<br>channel.position(newPos);<br></code></pre></td></tr></table></figure>
<p>设置当前位置时，如果设置为文件的末尾</p>
<ul>
<li>这时读取会返回 -1</li>
<li>这时写入，会追加内容，但要注意如果 position 超过了文件末尾，再写入时在新内容和原末尾之间会有空洞（00）</li>
</ul>
<h4 id="强制写入"><a href="#强制写入" class="headerlink" title="强制写入"></a>强制写入</h4><p>操作系统出于性能的考虑，会将数据缓存，不是立刻写入磁盘，而是等到缓存满了以后将所有数据一次性的写入磁盘。可以调用 force(true) 方法将文件内容和元数据（文件的权限等信息）立刻写入磁盘</p>
<h3 id="2-两个Channel传输数据"><a href="#2-两个Channel传输数据" class="headerlink" title="2.两个Channel传输数据"></a>2.两个Channel传输数据</h3><p><strong>transferTo方法</strong><br>使用transferTo方法可以快速、高效地讲义channel中的数据传输到另一个channel中，但一次只能传输2G的内容</p>
<p>transferTo底层使用了零拷贝技术</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestTransferTo</span></span>&#123;<br>	<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span></span>&#123;<br>		<span class="hljs-keyword">try</span>(FileInputStream from = <span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">&quot;from.txt&quot;</span>);<br>    		FileOutputStream to = <span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">&quot;to.txt&quot;</span>);<br>        FileChannel inputChannel = from.getChannel();<br>        FileChannel outputChannel = to.getChannel())&#123;<br>        <span class="hljs-comment">// 参数：inputChannel的起始位置，传输数据的大小，目的channel</span><br>        <span class="hljs-comment">// 返回值为传输的数据的字节数</span><br>        <span class="hljs-comment">// transferTo一次只能传输2G的数据</span><br>			inputChannel.transferTo(<span class="hljs-number">0</span>,inputChannel.size(),outputChannel);<br>		&#125;<span class="hljs-keyword">catch</span>(IOException e)&#123;<br>		e.printStackTrace();<br>		&#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<p>当传输的文件大于2G时，需要使用以下方法进行多次传输</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestChannel</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span></span>&#123;<br>        <span class="hljs-keyword">try</span> (FileInputStream from = <span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">&quot;from.txt&quot;</span>);<br>             FileOutputStream to = <span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">&quot;to.txt&quot;</span>);<br>             FileChannel inputChannel = from.getChannel();<br>             FileChannel outputChannel = to.getChannel()) &#123;<br>            <span class="hljs-keyword">long</span> size = inputChannel.size();<br>            <span class="hljs-keyword">long</span> capacity = inputChannel.size();<br>            <span class="hljs-comment">// 分多次传输</span><br>            <span class="hljs-keyword">while</span> (capacity &gt; <span class="hljs-number">0</span>) &#123;<br>                <span class="hljs-comment">// transferTo返回值为传输了的字节数</span><br>                capacity -= inputChannel.transferTo(size-capacity, capacity, outputChannel);<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<h3 id="3-Path与Paths"><a href="#3-Path与Paths" class="headerlink" title="3.Path与Paths"></a>3.Path与Paths</h3><ul>
<li>Path 用来表示文件路径</li>
<li>Paths 是工具类，用来获取 Path 实例<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java">Path source = Paths.get(<span class="hljs-string">&quot;1.txt&quot;</span>); <span class="hljs-comment">// 相对路径 不带盘符 使用 user.dir 环境变量来定位 1.txt</span><br><br>Path source = Paths.get(<span class="hljs-string">&quot;d:\\1.txt&quot;</span>); <span class="hljs-comment">// 绝对路径 代表了  d:\1.txt 反斜杠需要转义</span><br><br>Path source = Paths.get(<span class="hljs-string">&quot;d:/1.txt&quot;</span>); <span class="hljs-comment">// 绝对路径 同样代表了  d:\1.txt</span><br><br>Path projects = Paths.get(<span class="hljs-string">&quot;d:\\data&quot;</span>, <span class="hljs-string">&quot;projects&quot;</span>); <span class="hljs-comment">// 代表了  d:\data\projects</span><br></code></pre></td></tr></table></figure>
<h3 id="4-Files"><a href="#4-Files" class="headerlink" title="4.Files"></a>4.Files</h3><h4 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h4>检查文件是否存在<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">Path path = Paths.get(<span class="hljs-string">&quot;helloword/data.txt&quot;</span>);<br>System.out.println(Files.exists(path));<br></code></pre></td></tr></table></figure>
<h4 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h4>创建一级目录<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">Path path = Paths.get(<span class="hljs-string">&quot;helloword/d1&quot;</span>);<br>Files.createDirectory(path);<br></code></pre></td></tr></table></figure></li>
<li>如果目录已存在，会抛异常 FileAlreadyExistsException</li>
<li>不能一次创建多级目录，否则会抛异常 NoSuchFileException<br>创建多级目录用<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">Path path = Paths.get(<span class="hljs-string">&quot;helloword/d1/d2&quot;</span>);<br>Files.createDirectories(path);<br></code></pre></td></tr></table></figure>
<h4 id="拷贝及移动"><a href="#拷贝及移动" class="headerlink" title="拷贝及移动"></a>拷贝及移动</h4></li>
</ul>
<p><strong>拷贝文件</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">Path source = Paths.get(<span class="hljs-string">&quot;helloword/data.txt&quot;</span>);<br>Path target = Paths.get(<span class="hljs-string">&quot;helloword/target.txt&quot;</span>);<br>Files.copy(source, target);<br></code></pre></td></tr></table></figure>
<p>如果文件已存在，会抛异常 FileAlreadyExistsException<br>如果希望用 source 覆盖掉 target，需要用 StandardCopyOption 来控制</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);<br></code></pre></td></tr></table></figure>
<p><strong>移动文件</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">Path source = Paths.get(<span class="hljs-string">&quot;helloword/data.txt&quot;</span>);<br>Path target = Paths.get(<span class="hljs-string">&quot;helloword/data.txt&quot;</span>);<br>Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);<br></code></pre></td></tr></table></figure>
<p><strong>StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性</strong></p>
<h4 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h4><p><strong>删除文件</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">Path target = Paths.get(<span class="hljs-string">&quot;helloword/target.txt&quot;</span>);<br>Files.delete(target);<br></code></pre></td></tr></table></figure>
<ul>
<li>如果文件不存在，会抛异常NoSuchFileException</li>
</ul>
<p><strong>删除目录</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">Path target = Paths.get(<span class="hljs-string">&quot;helloword/d1&quot;</span>);<br>Files.delete(target);<br></code></pre></td></tr></table></figure>
<ul>
<li>如果目录还有内容，会抛异常 DirectoryNotEmptyException</li>
</ul>
<h4 id="遍历"><a href="#遍历" class="headerlink" title="遍历"></a>遍历</h4><p>可以使用Files工具类中的walkFileTree(Path, FileVisitor)方法，其中需要传入两个参数</p>
<ul>
<li>Path：文件起始路径</li>
<li>FileVisitor：文件访问器，使用访问者模式<ul>
<li>接口的实现类SimpleFileVisitor有四个方法<ul>
<li>preVisitDirectory：访问目录前的操作</li>
<li>visitFile：访问文件的操作</li>
<li>visitFileFailed：访问文件失败时的操作</li>
<li>postVisitDirectory：访问目录后的操作<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestWalkFileTree</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;<br>        Path path = Paths.get(<span class="hljs-string">&quot;F:\\JDK 8&quot;</span>);<br>        <span class="hljs-comment">// 文件目录数目</span><br>        AtomicInteger dirCount = <span class="hljs-keyword">new</span> AtomicInteger();<br>        <span class="hljs-comment">// 文件数目</span><br>        AtomicInteger fileCount = <span class="hljs-keyword">new</span> AtomicInteger();<br>        Files.walkFileTree(path, <span class="hljs-keyword">new</span> SimpleFileVisitor&lt;Path&gt;()&#123;<br>            <span class="hljs-meta">@Override</span><br>            <span class="hljs-function"><span class="hljs-keyword">public</span> FileVisitResult <span class="hljs-title">preVisitDirectory</span><span class="hljs-params">(Path dir, BasicFileAttributes attrs)</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;<br>                System.out.println(<span class="hljs-string">&quot;===&gt;&quot;</span>+dir);<br>                <span class="hljs-comment">// 增加文件目录数</span><br>                dirCount.incrementAndGet();<br>                <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.preVisitDirectory(dir, attrs);<br>            &#125;<br><br>            <span class="hljs-meta">@Override</span><br>            <span class="hljs-function"><span class="hljs-keyword">public</span> FileVisitResult <span class="hljs-title">visitFile</span><span class="hljs-params">(Path file, BasicFileAttributes attrs)</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;<br>                System.out.println(file);<br>                <span class="hljs-comment">// 增加文件数</span><br>                fileCount.incrementAndGet();<br>                <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.visitFile(file, attrs);<br>            &#125;<br>        &#125;);<br>        <span class="hljs-comment">// 打印数目</span><br>        System.out.println(<span class="hljs-string">&quot;文件目录数:&quot;</span>+dirCount.get());<br>        System.out.println(<span class="hljs-string">&quot;文件数:&quot;</span>+fileCount.get());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<h2 id="四、网络编程"><a href="#四、网络编程" class="headerlink" title="四、网络编程"></a>四、网络编程</h2></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="1、阻塞"><a href="#1、阻塞" class="headerlink" title="1、阻塞"></a>1、阻塞</h3><ul>
<li>阻塞模式下，相关方法都会导致线程暂停<ul>
<li>ServerSocketChannel.accept 会在没有连接建立时让线程暂停</li>
<li>SocketChannel.read 会在通道中没有数据可读时让线程暂停</li>
<li>阻塞的表现其实就是线程暂停了，暂停期间不会占用cpu，但线程相当于闲置</li>
</ul>
</li>
<li>单线程下，阻塞方法之间相会影响，几乎不能正常工作，需要多线程支持</li>
<li>但多线程下，有新的问题，体现在以下方面<ul>
<li>32位jvm一个线程320k，64位jvm一个线程1024k，如果连接数过多，必然导致OOM，并且线程太多，反而会因为频繁上下文切换导致性能降低</li>
<li>可以采用线程池技术来减少线程数和线程上下文切换，但治标不治本，如果有很多连接建立，但长时间inactive，会阻塞线程池中所有线程，因此不适合长连接，只适合短连接</li>
</ul>
</li>
</ul>
<p>代码来演示阻塞现象<br><strong>服务端代码</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Server</span></span>&#123;<br>	<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span></span>&#123;<br>		<span class="hljs-comment">//1.创建缓冲区</span><br>		ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">16</span>);<br>		<span class="hljs-comment">//2.创建服务器通道</span><br>		<span class="hljs-keyword">try</span>(ServerSocketChannel server = ServerSocketChannel.open())&#123;<br>			<span class="hljs-comment">//3.为服务器通道绑定端口</span><br>			server.bind(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-number">8000</span>));<br>			<span class="hljs-comment">//4.存放连接的集合</span><br>      ArrayList&lt;SocketChannel&gt; channels = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();<br>			<span class="hljs-comment">//循环接收连接</span><br>			<span class="hljs-keyword">while</span>(<span class="hljs-keyword">true</span>)&#123;<br>				System.out.println(<span class="hljs-string">&quot;before connecting...&quot;</span>);<br>				<span class="hljs-comment">//没有连接时，会阻塞线程</span><br>				SocketChannel socketChannel = server.accept();<br>        System.out.println(<span class="hljs-string">&quot;after connecting...&quot;</span>);<br>				channels.add(socketChannel);<br>				<span class="hljs-comment">// 循环遍历集合中的连接</span><br>				 <span class="hljs-keyword">for</span>(SocketChannel channel : channels) &#123;<br>         	System.out.println(<span class="hljs-string">&quot;before reading&quot;</span>);<br>         	<span class="hljs-comment">// 处理通道中的数据</span><br>         	<span class="hljs-comment">// 当通道中没有数据可读时，会阻塞线程</span><br>         	channel.read(buffer);<br>         	buffer.flip();<br>         	ByteBufferUtil.debugRead(buffer);<br>         	buffer.clear();<br>         	System.out.println(<span class="hljs-string">&quot;after reading&quot;</span>);<br>         &#125;<br>			&#125;<br>		&#125;<span class="hljs-keyword">catch</span>(IOException e)&#123;<br>				e.printStackTrace();<br>		&#125;<br>	&#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><strong>客户端代码</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> class <span class="hljs-title">Client</span><span class="hljs-params">()</span></span>&#123;<br>	<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span></span>&#123;<br>		<span class="hljs-keyword">try</span>(SocketChannel socketChannel = SocketChannel.open())&#123;<br>			<span class="hljs-comment">// 建立连接</span><br>      socketChannel.connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">&quot;localhost&quot;</span>, <span class="hljs-number">8080</span>));<br>			System.out.println(<span class="hljs-string">&quot;waiting...&quot;</span>);<br>		&#125;<span class="hljs-keyword">catch</span>(IOException e)&#123;<br>			e.printStackTrace();<br>		&#125;<br>	&#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>运行结果</p>
<ul>
<li>客户端连接之前：服务器端因accept阻塞</li>
<li>客户端-服务端建立连接后，客户端发送消息前：服务器端因通道为空被阻塞</li>
<li>客户端发送数据后，服务器处理通道中的数据。再次进入循环时，再次被accept阻塞</li>
<li>之前的客户端再次发送消息，服务器端因为被accept阻塞，无法处理之前客户端发送到通道中的信息<h3 id="2、非阻塞"><a href="#2、非阻塞" class="headerlink" title="2、非阻塞"></a>2、非阻塞</h3></li>
<li>可以同多ServerSocketChannel的configureBlocking(false)方法将获得连接设置为非阻塞的。此时若没有连接，，accept会返回null</li>
<li>可以通过SocketChannel的configureBlocking(false)方法将从通道中读取数据设置为非阻塞的。若此时通道中没有数据可读，read会返回-1</li>
</ul>
<p><strong>服务器代码如下</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Server</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>     <span class="hljs-comment">// 创建缓冲区</span><br>     ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">16</span>);<br>     <span class="hljs-comment">// 获得服务器通道</span><br>     <span class="hljs-keyword">try</span>(ServerSocketChannel server = ServerSocketChannel.open()) &#123;<br>      <span class="hljs-comment">// 为服务器通道绑定端口</span><br>      server.bind(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-number">8080</span>));<br>      <span class="hljs-comment">// 用户存放连接的集合</span><br>      ArrayList&lt;SocketChannel&gt; channels = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();<br>       <span class="hljs-comment">// 循环接收连接</span><br>       <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) &#123;<br>       <span class="hljs-comment">// 设置为非阻塞模式，没有连接时返回null，不会阻塞线程</span><br>       server.configureBlocking(<span class="hljs-keyword">false</span>);<br>       SocketChannel socketChannel = server.accept();<br>        <span class="hljs-comment">// 通道不为空时才将连接放入到集合中</span><br>        <span class="hljs-keyword">if</span> (socketChannel != <span class="hljs-keyword">null</span>) &#123;<br>       	 	System.out.println(<span class="hljs-string">&quot;after connecting...&quot;</span>);<br>        	channels.add(socketChannel);<br>        &#125;<br>        <span class="hljs-comment">// 循环遍历集合中的连接</span><br>        <span class="hljs-keyword">for</span>(SocketChannel channel : channels) &#123;<br>        <span class="hljs-comment">// 处理通道中的数据</span><br>        <span class="hljs-comment">// 设置为非阻塞模式，若通道中没有数据，会返回0，不会阻塞线程</span><br>        channel.configureBlocking(<span class="hljs-keyword">false</span>);<br>        <span class="hljs-keyword">int</span> read = channel.read(buffer);<br>        <span class="hljs-keyword">if</span>(read &gt; <span class="hljs-number">0</span>) &#123;<br>        buffer.flip();<br>        ByteBufferUtil.debugRead(buffer);<br>        buffer.clear();<br>        System.out.println(<span class="hljs-string">&quot;after reading&quot;</span>);<br>        		&#125;<br>        	&#125;<br>        &#125;<br>       &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<p>这样写存在一个问题，因为设置为了非阻塞，会一直执行while(true)中的代码，CPU一直处于忙碌状态，会使得性能变低，所以实际情况中不使用这种方法处理请求</p>
<h3 id="3、Selector"><a href="#3、Selector" class="headerlink" title="3、Selector"></a>3、Selector</h3><p><strong>多路复用</strong><br>单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控，这称之为多路复用</p>
<ul>
<li>多路复用仅针对网络 IO，普通文件 IO 无法利用多路复用</li>
<li>如果不用 Selector 的非阻塞模式，线程大部分时间都在做无用功，而 Selector 能够保证<ul>
<li>有可连接事件时才去连接</li>
<li>有可读事件才去读取</li>
<li>有可写事件才去写入<ul>
<li>限于网络传输能力，Channel 未必时时可写，一旦 Channel 可写，会触发 Selector 的可写事件</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>创建Selector</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Selector selector = Selector.open();<br></code></pre></td></tr></table></figure>

<p><strong>绑定 Channel 事件</strong><br>也称之为注册事件，绑定的事件 selector 才会关心 </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//必须将通道设置为非阻塞模式</span><br>channel.configureBlocking(<span class="hljs-keyword">false</span>);<br><span class="hljs-comment">// 将通道注册到选择器中，并设置感兴趣的实践</span><br>SelectionKey key = channel.register(selector, 绑定事件);<br></code></pre></td></tr></table></figure>
<ul>
<li>channel 必须工作在非阻塞模式</li>
<li>FileChannel 没有非阻塞模式，因此不能配合 selector 一起使用</li>
<li>绑定的事件类型可以有<ul>
<li>connect - 客户端连接成功时触发</li>
<li>accept - 服务器端成功接受连接时触发</li>
<li>read - 数据可读入时触发，有因为接收能力弱，数据暂不能读入的情况</li>
<li>write - 数据可写出时触发，有因为发送能力弱，数据暂不能写出的情况</li>
</ul>
</li>
</ul>
<p><strong>监听 Channel 事件</strong><br>可以通过下面三种方法来监听是否有事件发生，方法的返回值代表有多少 channel 发生了事件<br>方法1，阻塞直到绑定事件发生</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> count = selector.select();<br></code></pre></td></tr></table></figure>
<p>方法2，阻塞直到绑定事件发生，或是超时（时间单位为 ms）</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> count = selector.select(<span class="hljs-keyword">long</span> timeout);<br></code></pre></td></tr></table></figure>
<p>方法3，不会阻塞，也就是不管有没有事件，立刻返回，自己根据返回值检查是否有事件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> count = selector.selectNow();<br></code></pre></td></tr></table></figure>
<h3 id="4、Accpet事件"><a href="#4、Accpet事件" class="headerlink" title="4、Accpet事件"></a>4、Accpet事件</h3><p>要使用Selector实现多路复用，服务端代码如下改进</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SelectServer</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>    		<span class="hljs-comment">//获取缓冲区</span><br>        ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">16</span>);<br>        <span class="hljs-comment">// 获得服务器通道</span><br>        <span class="hljs-keyword">try</span>(ServerSocketChannel server = ServerSocketChannel.open()) &#123;<br>            server.bind(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-number">8080</span>));<br>            <span class="hljs-comment">// 创建选择器</span><br>            Selector selector = Selector.open();<br>            <span class="hljs-comment">// 通道必须设置为非阻塞模式</span><br>            server.configureBlocking(<span class="hljs-keyword">false</span>);<br>            <span class="hljs-comment">// 将通道注册到选择器中，并设置感兴趣的事件</span><br>            server.register(selector, SelectionKey.OP_ACCEPT);<br>            <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) &#123;<br>                <span class="hljs-comment">// 若没有事件就绪，线程会被阻塞，反之不会被阻塞。从而避免了CPU空转</span><br>                <span class="hljs-comment">// 返回值为就绪的事件个数</span><br>                <span class="hljs-keyword">int</span> ready = selector.select();<br>                System.out.println(<span class="hljs-string">&quot;selector ready counts : &quot;</span> + ready);<br>                <br>                <span class="hljs-comment">// 获取所有事件</span><br>                Set&lt;SelectionKey&gt; selectionKeys = selector.selectedKeys();<br>                <br>                <span class="hljs-comment">// 使用迭代器遍历事件</span><br>                Iterator&lt;SelectionKey&gt; iterator = selectionKeys.iterator();<br>                <span class="hljs-keyword">while</span> (iterator.hasNext()) &#123;<br>                    SelectionKey key = iterator.next();<br>                    <br>                    <span class="hljs-comment">// 判断key的类型</span><br>                    <span class="hljs-keyword">if</span>(key.isAcceptable()) &#123;<br>                        <span class="hljs-comment">// 获得key对应的channel</span><br>                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();<br>                        System.out.println(<span class="hljs-string">&quot;before accepting...&quot;</span>);<br>                        <br>        				<span class="hljs-comment">// 获取连接并处理，而且是必须处理，否则需要取消</span><br>                        SocketChannel socketChannel = channel.accept();<br>                        System.out.println(<span class="hljs-string">&quot;after accepting...&quot;</span>);<br>                        <br>                        <span class="hljs-comment">// 处理完毕后，必须将事件移除</span><br>                        iterator.remove();<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<p>事件发生后能否不处理</p>
<p>事件发生后，要么处理，要么取消（cancel），不能什么都不做，否则下次该事件仍会触发，这是因为 nio 底层使用的是水平触发</p>
<h3 id="5、Read事件"><a href="#5、Read事件" class="headerlink" title="5、Read事件"></a>5、Read事件</h3><ul>
<li>在Accept事件中，若有客户端与服务器端建立了连接，需要将其对应的SocketChannel设置为非阻塞，并注册到选择其中</li>
<li>添加Read事件，触发后进行读取操作<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SelectServer</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>        ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">16</span>);<br>        <span class="hljs-comment">// 获得服务器通道</span><br>        <span class="hljs-keyword">try</span>(ServerSocketChannel server = ServerSocketChannel.open()) &#123;<br>            server.bind(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-number">8080</span>));<br>            <span class="hljs-comment">// 创建选择器</span><br>            Selector selector = Selector.open();<br>            <span class="hljs-comment">// 通道必须设置为非阻塞模式</span><br>            server.configureBlocking(<span class="hljs-keyword">false</span>);<br>            <span class="hljs-comment">// 将通道注册到选择器中，并设置感兴趣的实践</span><br>            server.register(selector, SelectionKey.OP_ACCEPT);<br>            <span class="hljs-comment">// 为serverKey设置感兴趣的事件</span><br>            <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) &#123;<br>                <span class="hljs-comment">// 若没有事件就绪，线程会被阻塞，反之不会被阻塞。从而避免了CPU空转</span><br>                <span class="hljs-comment">// 返回值为就绪的事件个数</span><br>                <span class="hljs-keyword">int</span> ready = selector.select();<br>                System.out.println(<span class="hljs-string">&quot;selector ready counts : &quot;</span> + ready);<br>                <span class="hljs-comment">// 获取所有事件</span><br>                Set&lt;SelectionKey&gt; selectionKeys = selector.selectedKeys();<br>                <span class="hljs-comment">// 使用迭代器遍历事件</span><br>                Iterator&lt;SelectionKey&gt; iterator = selectionKeys.iterator();<br>                <span class="hljs-keyword">while</span> (iterator.hasNext()) &#123;<br>                    SelectionKey key = iterator.next();<br>                    <span class="hljs-comment">// 判断key的类型</span><br>                    <span class="hljs-keyword">if</span>(key.isAcceptable()) &#123;<br>                        <span class="hljs-comment">// 获得key对应的channel</span><br>                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();<br>                        System.out.println(<span class="hljs-string">&quot;before accepting...&quot;</span>);<br>                        <span class="hljs-comment">// 获取连接</span><br>                        SocketChannel socketChannel = channel.accept();<br>                        System.out.println(<span class="hljs-string">&quot;after accepting...&quot;</span>);<br>                        <span class="hljs-comment">// 设置为非阻塞模式，同时将连接的通道也注册到选择其中</span><br>                        socketChannel.configureBlocking(<span class="hljs-keyword">false</span>);<br>                        socketChannel.register(selector, SelectionKey.OP_READ);<br>                        <span class="hljs-comment">// 处理完毕后移除</span><br>                        iterator.remove();<br>                    &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (key.isReadable()) &#123;<br>                        SocketChannel channel = (SocketChannel) key.channel();<br>                        System.out.println(<span class="hljs-string">&quot;before reading...&quot;</span>);<br>                        channel.read(buffer);<br>                        System.out.println(<span class="hljs-string">&quot;after reading...&quot;</span>);<br>                        buffer.flip();<br>                        ByteBufferUtil.debugRead(buffer);<br>                        buffer.clear();<br>                        <span class="hljs-comment">// 处理完毕后移除</span><br>                        iterator.remove();<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li>
</ul>
<p>当处理完一个事件后，一定要调用迭代器的remove方法移除对应事件，否则会出现错误。原因如下<br>以我们上面的** Read事件**的代码为例</p>
<ul>
<li><p>当调用了 server.register(selector, SelectionKey.OP_ACCEPT)后，Selector中维护了一个集合，用于存放SelectionKey以及其对应的通道</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// WindowsSelectorImpl 中的 SelectionKeyImpl数组</span><br><span class="hljs-keyword">private</span> SelectionKeyImpl[] channelArray = <span class="hljs-keyword">new</span> SelectionKeyImpl[<span class="hljs-number">8</span>];<br></code></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SelectionKeyImpl</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractSelectionKey</span> </span>&#123;<br>    <span class="hljs-comment">// Key对应的通道</span><br>    <span class="hljs-keyword">final</span> SelChImpl channel;<br>    ...<br>&#125;<br></code></pre></td></tr></table></figure>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210414192429.png" srcset="/blogshow/img/loading.gif" lazyload alt="20210414192429"></p>
</li>
<li><p>当选择器中的通道对应的事件发生后，selecionKey会被放到另一个集合中，但是selecionKey不会自动移除，所以需要我们在处理完一个事件后，通过迭代器手动移除其中的selecionKey。否则会导致已被处理过的事件再次被处理，就会引发错误</p>
</li>
</ul>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210414193143.png" srcset="/blogshow/img/loading.gif" lazyload alt="20210414193143"></p>
<p> <strong>cancel 的作用</strong></p>
<blockquote>
<p>cancel 会取消注册在 selector 上的 channel，并从 keys 集合中删除 key 后续不会再监听事件</p>
</blockquote>
<h4 id="5-1断开处理"><a href="#5-1断开处理" class="headerlink" title="5.1断开处理"></a>5.1<strong>断开处理</strong></h4><p>​    当客户端与服务器之间的连接断开时，会给服务器端发送一个读事件，对异常断开和正常断开需要加以不同的方式进行处理</p>
<ul>
<li><strong>正常断开</strong><ul>
<li>正常断开时，服务器端的channel.read(buffer)方法的返回值为-1，所以当结束到返回值为-1时，需要调用key的cancel方法取消此事件，并在取消后移除该事件<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> read = channel.read(buffer);<br><span class="hljs-comment">// 断开连接时，客户端会向服务器发送一个写事件，此时read的返回值为-1</span><br><span class="hljs-keyword">if</span>(read == -<span class="hljs-number">1</span>) &#123;<br>    <span class="hljs-comment">// 取消该事件的处理</span><br>	key.cancel();<br>    channel.close();<br>&#125; <span class="hljs-keyword">else</span> &#123;<br>    ...<br>&#125;<br><span class="hljs-comment">// 取消或者处理，都需要移除key</span><br>iterator.remove();<br></code></pre></td></tr></table></figure></li>
</ul>
</li>
<li><strong>异常断开</strong><ul>
<li>异常断开时，会抛出IOException异常， 在try-catch的catch块中捕获异常并调用key的cancel方法即可</li>
</ul>
</li>
</ul>
<h4 id="5-2消息边界"><a href="#5-2消息边界" class="headerlink" title="5.2消息边界"></a>5.2消息边界</h4><p><strong>不处理消息边界存在的问题</strong><br>将缓冲区的大小设置为4个字节，发送2个汉字（你好），通过decode解码并打印时，会出现乱码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">4</span>);<br><span class="hljs-comment">// 解码并打印</span><br>System.out.println(StandardCharsets.UTF_8.decode(buffer));<br></code></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">你�<br>��<br></code></pre></td></tr></table></figure>
<p>这是因为UTF-8字符集下，1个汉字占用3个字节，此时缓冲区大小为4个字节，一次读时间无法处理完通道中的所有数据，所以一共会触发两次读事件。这就导致 你好 的 好 字被拆分为了前半部分和后半部分发送，解码时就会出现问题</p>
<p><strong>处理消息边界</strong><br>传输的文本可能有以下三种情况</p>
<ul>
<li>文本大于缓冲区大小<ul>
<li>此时需要将缓冲区进行扩容</li>
</ul>
</li>
<li>发生半包现象</li>
<li>发生粘包现象<img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210415103442.png" srcset="/blogshow/img/loading.gif" lazyload alt="20210415103442"></li>
</ul>
<p>解决思路大致有以下三种</p>
<ul>
<li>固定消息长度，数据包大小一样，服务器按预定长度读取，当发送的数据较少时，需要将数据进行填充，直到长度与消息规定长度一致。缺点是浪费带宽</li>
<li>另一种思路是按分隔符拆分，缺点是效率低，需要一个一个字符地去匹配分隔符</li>
<li>TLV 格式，即 Type 类型、Length 长度、Value 数据（也就是在消息开头用一些空间存放后面数据的长度），如HTTP请求头中的Content-Type与Content-Length。类型和长度已知的情况下，就可以方便获取消息大小，分配合适的 buffer，缺点是 buffer 需要提前分配，如果内容过大，则影响 server 吞吐量</li>
</ul>
<p>下文的消息边界处理方式为<strong>第二种：按分隔符拆分</strong></p>
<p><strong>附件与扩容</strong><br>Channel的register方法还有第三个参数：附件，可以向其中放入一个Object类型的对象，该对象会与登记的Channel以及其对应的SelectionKey绑定，可以从SelectionKey获取到对应通道的附件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> SelectionKey <span class="hljs-title">register</span><span class="hljs-params">(Selector sel, <span class="hljs-keyword">int</span> ops, Object att)</span></span><br></code></pre></td></tr></table></figure>
<p>可通过SelectionKey的attachment()方法获得附件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">ByteBuffer buffer = (ByteBuffer) key.attachment();<br></code></pre></td></tr></table></figure>
<p>我们需要在Accept事件发生后，将通道注册到Selector中时，对每个通道添加一个ByteBuffer附件，让每个通道发生读事件时都使用自己的通道，避免与其他通道发生冲突而导致问题</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 设置为非阻塞模式，同时将连接的通道也注册到选择其中，同时设置附件</span><br>socketChannel.configureBlocking(<span class="hljs-keyword">false</span>);<br>ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">16</span>);<br><span class="hljs-comment">// 添加通道对应的Buffer附件</span><br>socketChannel.register(selector, SelectionKey.OP_READ, buffer);<br></code></pre></td></tr></table></figure>
<p>当Channel中的数据大于缓冲区时，需要对缓冲区进行扩容操作。此代码中的扩容的判定方法：Channel调用compact方法后，position与limit相等，说明缓冲区中的数据并未被读取（容量太小），此时创建新的缓冲区，其大小扩大为两倍。同时还要将旧缓冲区中的数据拷贝到新的缓冲区中，同时调用SelectionKey的attach方法将新的缓冲区作为新的附件放入SelectionKey中</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 如果缓冲区太小，就进行扩容</span><br><span class="hljs-keyword">if</span> (buffer.position() == buffer.limit()) &#123;<br>    ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity()*<span class="hljs-number">2</span>);<br>    <span class="hljs-comment">// 将旧buffer中的内容放入新的buffer中</span><br>    ewBuffer.put(buffer);<br>    <span class="hljs-comment">// 将新buffer作为附件放到key中</span><br>    key.attach(newBuffer);<br>&#125;<br></code></pre></td></tr></table></figure>
<p>改造后的服务器代码如下</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SelectServer</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>        <span class="hljs-comment">// 获得服务器通道</span><br>        <span class="hljs-keyword">try</span>(ServerSocketChannel server = ServerSocketChannel.open()) &#123;<br>            server.bind(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-number">8080</span>));<br>            <span class="hljs-comment">// 创建选择器</span><br>            Selector selector = Selector.open();<br>            <span class="hljs-comment">// 通道必须设置为非阻塞模式</span><br>            server.configureBlocking(<span class="hljs-keyword">false</span>);<br>            <span class="hljs-comment">// 将通道注册到选择器中，并设置感兴趣的事件</span><br>            server.register(selector, SelectionKey.OP_ACCEPT);<br>            <span class="hljs-comment">// 为serverKey设置感兴趣的事件</span><br>            <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) &#123;<br>                <span class="hljs-comment">// 若没有事件就绪，线程会被阻塞，反之不会被阻塞。从而避免了CPU空转</span><br>                <span class="hljs-comment">// 返回值为就绪的事件个数</span><br>                <span class="hljs-keyword">int</span> ready = selector.select();<br>                System.out.println(<span class="hljs-string">&quot;selector ready counts : &quot;</span> + ready);<br>                <span class="hljs-comment">// 获取所有事件</span><br>                Set&lt;SelectionKey&gt; selectionKeys = selector.selectedKeys();<br>                <span class="hljs-comment">// 使用迭代器遍历事件</span><br>                Iterator&lt;SelectionKey&gt; iterator = selectionKeys.iterator();<br>                <span class="hljs-keyword">while</span> (iterator.hasNext()) &#123;<br>                    SelectionKey key = iterator.next();<br>                    <span class="hljs-comment">// 判断key的类型</span><br>                    <span class="hljs-keyword">if</span>(key.isAcceptable()) &#123;<br>                        <span class="hljs-comment">// 获得key对应的channel</span><br>                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();<br>                        System.out.println(<span class="hljs-string">&quot;before accepting...&quot;</span>);<br>                        <span class="hljs-comment">// 获取连接</span><br>                        SocketChannel socketChannel = channel.accept();<br>                        System.out.println(<span class="hljs-string">&quot;after accepting...&quot;</span>);<br>                        <span class="hljs-comment">// 设置为非阻塞模式，同时将连接的通道也注册到选择其中，同时设置附件</span><br>                        socketChannel.configureBlocking(<span class="hljs-keyword">false</span>);<br>                        ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">16</span>);<br>                        socketChannel.register(selector, SelectionKey.OP_READ, buffer);<br>                        <span class="hljs-comment">// 处理完毕后移除</span><br>                        iterator.remove();<br>                    &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (key.isReadable()) &#123;<br>                        SocketChannel channel = (SocketChannel) key.channel();<br>                        System.out.println(<span class="hljs-string">&quot;before reading...&quot;</span>);<br>                        <span class="hljs-comment">// 通过key获得附件（buffer）</span><br>                        ByteBuffer buffer = (ByteBuffer) key.attachment();<br>                        <span class="hljs-keyword">int</span> read = channel.read(buffer);<br>                        <span class="hljs-keyword">if</span>(read == -<span class="hljs-number">1</span>) &#123;<br>                            key.cancel();<br>                            channel.close();<br>                        &#125; <span class="hljs-keyword">else</span> &#123;<br>                            <span class="hljs-comment">// 通过分隔符来分隔buffer中的数据</span><br>                            split(buffer);<br>                            <span class="hljs-comment">// 如果缓冲区太小，就进行扩容</span><br>                            <span class="hljs-keyword">if</span> (buffer.position() == buffer.limit()) &#123;<br>                                ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity()*<span class="hljs-number">2</span>);<br>                                <span class="hljs-comment">// 将旧buffer中的内容放入新的buffer中</span><br>                                buffer.flip();<br>                                newBuffer.put(buffer);<br>                                <span class="hljs-comment">// 将新buffer放到key中作为附件</span><br>                                key.attach(newBuffer);<br>                            &#125;<br>                        &#125;<br>                        System.out.println(<span class="hljs-string">&quot;after reading...&quot;</span>);<br>                        <span class="hljs-comment">// 处理完毕后移除</span><br>                        iterator.remove();<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">split</span><span class="hljs-params">(ByteBuffer buffer)</span> </span>&#123;<br>        buffer.flip();<br>        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; buffer.limit(); i++) &#123;<br>            <span class="hljs-comment">// 遍历寻找分隔符</span><br>            <span class="hljs-comment">// get(i)不会移动position</span><br>            <span class="hljs-keyword">if</span> (buffer.get(i) == <span class="hljs-string">&#x27;\n&#x27;</span>) &#123;<br>                <span class="hljs-comment">// 缓冲区长度</span><br>                <span class="hljs-keyword">int</span> length = i+<span class="hljs-number">1</span>-buffer.position();<br>                ByteBuffer target = ByteBuffer.allocate(length);<br>                <span class="hljs-comment">// 将前面的内容写入target缓冲区</span><br>                <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; length; j++) &#123;<br>                    <span class="hljs-comment">// 将buffer中的数据写入target中</span><br>                    target.put(buffer.get());<br>                &#125;<br>                <span class="hljs-comment">// 打印结果</span><br>                ByteBufferUtil.debugAll(target);<br>            &#125;<br>        &#125;<br>        <span class="hljs-comment">// 切换为写模式，但是缓冲区可能未读完，这里需要使用compact</span><br>        buffer.compact();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><strong>ByteBuffer的大小分配</strong></p>
<ul>
<li>每个 channel 都需要记录可能被切分的消息，因为 <strong>ByteBuffer 不能被多个 channel 共同使用</strong>，因此需要为每个 channel 维护一个独立的 ByteBuffer</li>
<li>ByteBuffer 不能太大，比如一个 ByteBuffer 1Mb 的话，要支持百万连接就要 1Tb 内存，因此需要设计大小可变的 ByteBuffer</li>
<li>分配思路可以参考<ul>
<li>一种思路是首先分配一个较小的 buffer，例如 4k，如果发现数据不够，再分配 8k 的 buffer，将 4k buffer 内容拷贝至 8k buffer，优点是消息连续容易处理，缺点是数据拷贝耗费性能<ul>
<li>参考实现 <a target="_blank" rel="noopener" href="http://tutorials.jenkov.com/java-performance/resizable-array.html">http://tutorials.jenkov.com/java-performance/resizable-array.html</a></li>
</ul>
</li>
<li>另一种思路是用多个数组组成 buffer，一个数组不够，把多出来的内容写入新的数组，与前面的区别是消息存储不连续解析复杂，优点是避免了拷贝引起的性能损耗</li>
</ul>
</li>
</ul>
<h3 id="6-write事件"><a href="#6-write事件" class="headerlink" title="6.write事件"></a>6.write事件</h3><p>服务器通过buffer向通道中写入数据时，可能因为通道容量小于Buffer中的数据大小，导致无法一次性将Buffer中的数据全部卸乳到Channel中，这时便需要分多次写入，具体步骤如下</p>
<ul>
<li>执行一次写操作，向将buffer中的内容写入到SocketChannel中，然后判断Buffer中是否还有数据</li>
<li>若Buffer中还有数据，则需要将SocketChannel注册到Seletor中，并关注写事件，同时将未写完的Buffer作为附件一起放入到SelectionKey中<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> write = socket.write(buffer);<br><span class="hljs-comment">// 通道中可能无法放入缓冲区中的所有数据</span><br><span class="hljs-keyword">if</span> (buffer.hasRemaining()) &#123;<br>    <span class="hljs-comment">// 注册到Selector中，关注可写事件，并将buffer添加到key的附件中</span><br>    socket.configureBlocking(<span class="hljs-keyword">false</span>);<br>    socket.register(selector, SelectionKey.OP_WRITE, buffer);<br>&#125;<br></code></pre></td></tr></table></figure></li>
<li>添加写事件的相关操作key。isWritable()，对Buffer再次进行写操作<ul>
<li>每次写后需要判断Buffer中是否还有数据，若写完，需要移除SelectionKey中的Buffer附件，避免其占用过多内存，同时还需移除对写事件的关注<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java">SocketChannel socket = (SocketChannel) key.channel();<br><span class="hljs-comment">// 获得buffer</span><br>ByteBuffer buffer = (ByteBuffer) key.attachment();<br><span class="hljs-comment">// 执行写操作</span><br><span class="hljs-keyword">int</span> write = socket.write(buffer);<br>System.out.println(write);<br><span class="hljs-comment">// 如果已经完成了写操作，需要移除key中的附件，同时不再对写事件感兴趣</span><br><span class="hljs-keyword">if</span> (!buffer.hasRemaining()) &#123;<br>    key.attach(<span class="hljs-keyword">null</span>);<br>    key.interestOps(<span class="hljs-number">0</span>);<br>&#125;<br></code></pre></td></tr></table></figure></li>
</ul>
</li>
</ul>
<p><strong>整体代码如下</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WriteServer</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>        <span class="hljs-keyword">try</span>(ServerSocketChannel server = ServerSocketChannel.open()) &#123;<br>            server.bind(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-number">8080</span>));<br>            server.configureBlocking(<span class="hljs-keyword">false</span>);<br>            Selector selector = Selector.open();<br>            server.register(selector, SelectionKey.OP_ACCEPT);<br>            <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) &#123;<br>                selector.select();<br>                Set&lt;SelectionKey&gt; selectionKeys = selector.selectedKeys();<br>                Iterator&lt;SelectionKey&gt; iterator = selectionKeys.iterator();<br>                <span class="hljs-keyword">while</span> (iterator.hasNext()) &#123;<br>                    SelectionKey key = iterator.next();<br>                    <span class="hljs-comment">// 处理后就移除事件</span><br>                    iterator.remove();<br>                    <span class="hljs-keyword">if</span> (key.isAcceptable()) &#123;<br>                        <span class="hljs-comment">// 获得客户端的通道</span><br>                        SocketChannel socket = server.accept();<br>                        <span class="hljs-comment">// 写入数据</span><br>                        StringBuilder builder = <span class="hljs-keyword">new</span> StringBuilder();<br>                        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">500000000</span>; i++) &#123;<br>                            builder.append(<span class="hljs-string">&quot;a&quot;</span>);<br>                        &#125;<br>                        ByteBuffer buffer = StandardCharsets.UTF_8.encode(builder.toString());<br>                        <span class="hljs-comment">// 先执行一次Buffer-&gt;Channel的写入，如果未写完，就添加一个可写事件</span><br>                        <span class="hljs-keyword">int</span> write = socket.write(buffer);<br>                        System.out.println(write);<br>                        <span class="hljs-comment">// 通道中可能无法放入缓冲区中的所有数据</span><br>                        <span class="hljs-keyword">if</span> (buffer.hasRemaining()) &#123;<br>                            <span class="hljs-comment">// 注册到Selector中，关注可写事件，并将buffer添加到key的附件中</span><br>                            socket.configureBlocking(<span class="hljs-keyword">false</span>);<br>                            socket.register(selector, SelectionKey.OP_WRITE, buffer);<br>                        &#125;<br>                    &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (key.isWritable()) &#123;<br>                        SocketChannel socket = (SocketChannel) key.channel();<br>                        <span class="hljs-comment">// 获得buffer</span><br>                        ByteBuffer buffer = (ByteBuffer) key.attachment();<br>                        <span class="hljs-comment">// 执行写操作</span><br>                        <span class="hljs-keyword">int</span> write = socket.write(buffer);<br>                        System.out.println(write);<br>                        <span class="hljs-comment">// 如果已经完成了写操作，需要移除key中的附件，同时不再对写事件感兴趣</span><br>                        <span class="hljs-keyword">if</span> (!buffer.hasRemaining()) &#123;<br>                            key.attach(<span class="hljs-keyword">null</span>);<br>                            key.interestOps(<span class="hljs-number">0</span>);<br>                        &#125;<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<h3 id="7-优化"><a href="#7-优化" class="headerlink" title="7.优化"></a>7.优化</h3><p><strong>多线程优化</strong><br>充分利用多核CPU，分两组选择器</p>
<ul>
<li>单线程配一个选择器（Boss），专门处理accept事件</li>
<li>创建cpu核心数的线程（Worker），每个线程配一个选择器，轮流处理read事件</li>
</ul>
<p><strong>实现思路</strong></p>
<ul>
<li>创建一个负责处理accept事件的boss线程，与多个负责处理read事件的worker线程</li>
<li>boss线程执行的操作<ul>
<li>接受并处理accept事件，当accept事件发生后，调用worker的register（Socket Channel socket）方法，让worker去处理read事件，其中需要根据表示robin去判断将任务分配给那个worker<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 创建固定数量的Worker</span><br>Worker[] workers = <span class="hljs-keyword">new</span> Worker[<span class="hljs-number">4</span>];<br><span class="hljs-comment">// 用于负载均衡的原子整数</span><br>AtomicInteger robin = <span class="hljs-keyword">new</span> AtomicInteger(<span class="hljs-number">0</span>);<br><span class="hljs-comment">// 负载均衡，轮询分配Worker</span><br>workers[robin.getAndIncrement()% workers.length].register(socket);<br></code></pre></td></tr></table></figure></li>
<li>register(SocketChannel socket)方法会通过同步队列完成Boss线程与Worker线程之间的通信，让SocketChannel的注册任务被Worker线程执行。添加任务后需要调用selector.wakeup()来唤醒被阻塞的Selector<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">register</span><span class="hljs-params">(<span class="hljs-keyword">final</span> SocketChannel socket)</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;<br>    <span class="hljs-comment">// 只启动一次</span><br>    <span class="hljs-keyword">if</span> (!started) &#123;<br>       <span class="hljs-comment">// 初始化操作</span><br>    &#125;<br>    <span class="hljs-comment">// 向同步队列中添加SocketChannel的注册事件</span><br>    <span class="hljs-comment">// 在Worker线程中执行注册事件</span><br>    queue.add(<span class="hljs-keyword">new</span> Runnable() &#123;<br>        <span class="hljs-meta">@Override</span><br>        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                socket.register(selector, SelectionKey.OP_READ);<br>            &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>                e.printStackTrace();<br>            &#125;<br>        &#125;<br>    &#125;);<br>    <span class="hljs-comment">// 唤醒被阻塞的Selector</span><br>    <span class="hljs-comment">// select类似LockSupport中的park，wakeup的原理类似LockSupport中的unpark</span><br>    selector.wakeup();<br>&#125;<br></code></pre></td></tr></table></figure></li>
</ul>
</li>
<li>Worker线程执行的操作<ul>
<li>从同步队列中获取注册任务，并处理Read事件</li>
</ul>
  <strong>实现代码</strong><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ThreadsServer</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>        <span class="hljs-keyword">try</span> (ServerSocketChannel server = ServerSocketChannel.open()) &#123;<br>            <span class="hljs-comment">// 当前线程为Boss线程</span><br>            Thread.currentThread().setName(<span class="hljs-string">&quot;Boss&quot;</span>);<br>            server.bind(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-number">8080</span>));<br>            <span class="hljs-comment">// 负责轮询Accept事件的Selector</span><br>            Selector boss = Selector.open();<br>            server.configureBlocking(<span class="hljs-keyword">false</span>);<br>            server.register(boss, SelectionKey.OP_ACCEPT);<br>            <span class="hljs-comment">// 创建固定数量的Worker</span><br>            Worker[] workers = <span class="hljs-keyword">new</span> Worker[<span class="hljs-number">4</span>];<br>            <span class="hljs-comment">// 用于负载均衡的原子整数</span><br>            AtomicInteger robin = <span class="hljs-keyword">new</span> AtomicInteger(<span class="hljs-number">0</span>);<br>            <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; workers.length; i++) &#123;<br>                workers[i] = <span class="hljs-keyword">new</span> Worker(<span class="hljs-string">&quot;worker-&quot;</span>+i);<br>            &#125;<br>            <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) &#123;<br>                boss.select();<br>                Set&lt;SelectionKey&gt; selectionKeys = boss.selectedKeys();<br>                Iterator&lt;SelectionKey&gt; iterator = selectionKeys.iterator();<br>                <span class="hljs-keyword">while</span> (iterator.hasNext()) &#123;<br>                    SelectionKey key = iterator.next();<br>                    iterator.remove();<br>                    <span class="hljs-comment">// BossSelector负责Accept事件</span><br>                    <span class="hljs-keyword">if</span> (key.isAcceptable()) &#123;<br>                        <span class="hljs-comment">// 建立连接</span><br>                        SocketChannel socket = server.accept();<br>                        System.out.println(<span class="hljs-string">&quot;connected...&quot;</span>);<br>                        socket.configureBlocking(<span class="hljs-keyword">false</span>);<br>                        <span class="hljs-comment">// socket注册到Worker的Selector中</span><br>                        System.out.println(<span class="hljs-string">&quot;before read...&quot;</span>);<br>                        <span class="hljs-comment">// 负载均衡，轮询分配Worker</span><br>                        workers[robin.getAndIncrement()% workers.length].register(socket);<br>                        System.out.println(<span class="hljs-string">&quot;after read...&quot;</span>);<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Worker</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Runnable</span> </span>&#123;<br>        <span class="hljs-keyword">private</span> Thread thread;<br>        <span class="hljs-keyword">private</span> <span class="hljs-keyword">volatile</span> Selector selector;<br>        <span class="hljs-keyword">private</span> String name;<br>        <span class="hljs-keyword">private</span> <span class="hljs-keyword">volatile</span> <span class="hljs-keyword">boolean</span> started = <span class="hljs-keyword">false</span>;<br>        <span class="hljs-comment">/**</span><br><span class="hljs-comment">         * 同步队列，用于Boss线程与Worker线程之间的通信</span><br><span class="hljs-comment">         */</span><br>        <span class="hljs-keyword">private</span> ConcurrentLinkedQueue&lt;Runnable&gt; queue;<br><br>        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Worker</span><span class="hljs-params">(String name)</span> </span>&#123;<br>            <span class="hljs-keyword">this</span>.name = name;<br>        &#125;<br><br>        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">register</span><span class="hljs-params">(<span class="hljs-keyword">final</span> SocketChannel socket)</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;<br>            <span class="hljs-comment">// 只启动一次</span><br>            <span class="hljs-keyword">if</span> (!started) &#123;<br>                thread = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">this</span>, name);<br>                selector = Selector.open();<br>                queue = <span class="hljs-keyword">new</span> ConcurrentLinkedQueue&lt;&gt;();<br>                thread.start();<br>                started = <span class="hljs-keyword">true</span>;<br>            &#125;<br>            <br>            <span class="hljs-comment">// 向同步队列中添加SocketChannel的注册事件</span><br>            <span class="hljs-comment">// 在Worker线程中执行注册事件</span><br>            queue.add(<span class="hljs-keyword">new</span> Runnable() &#123;<br>                <span class="hljs-meta">@Override</span><br>                <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;<br>                    <span class="hljs-keyword">try</span> &#123;<br>                        socket.register(selector, SelectionKey.OP_READ);<br>                    &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>                        e.printStackTrace();<br>                    &#125;<br>                &#125;<br>            &#125;);<br>            <span class="hljs-comment">// 唤醒被阻塞的Selector</span><br>            <span class="hljs-comment">// select类似LockSupport中的park，wakeup的原理类似LockSupport中的unpark</span><br>            selector.wakeup();<br>        &#125;<br><br>        <span class="hljs-meta">@Override</span><br>        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;<br>            <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) &#123;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    selector.select();<br>                    <span class="hljs-comment">// 通过同步队列获得任务并运行</span><br>                    Runnable task = queue.poll();<br>                    <span class="hljs-keyword">if</span> (task != <span class="hljs-keyword">null</span>) &#123;<br>                        <span class="hljs-comment">// 获得任务，执行注册操作</span><br>                        task.run();<br>                    &#125;<br>                    Set&lt;SelectionKey&gt; selectionKeys = selector.selectedKeys();<br>                    Iterator&lt;SelectionKey&gt; iterator = selectionKeys.iterator();<br>                    <span class="hljs-keyword">while</span>(iterator.hasNext()) &#123;<br>                        SelectionKey key = iterator.next();<br>                        iterator.remove();<br>                        <span class="hljs-comment">// Worker只负责Read事件</span><br>                        <span class="hljs-keyword">if</span> (key.isReadable()) &#123;<br>                            <span class="hljs-comment">// 简化处理，省略细节</span><br>                            SocketChannel socket = (SocketChannel) key.channel();<br>                            ByteBuffer buffer = ByteBuffer.allocate(<span class="hljs-number">16</span>);<br>                            socket.read(buffer);<br>                            buffer.flip();<br>                            ByteBufferUtil.debugAll(buffer);<br>                        &#125;<br>                    &#125;<br>                &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>                    e.printStackTrace();<br>                &#125;<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li>
</ul>
<h2 id="五、NIO-vs-BIO"><a href="#五、NIO-vs-BIO" class="headerlink" title="五、NIO vs BIO"></a>五、NIO vs BIO</h2><h3 id="1-stream-vs-channel"><a href="#1-stream-vs-channel" class="headerlink" title="1.stream vs channel"></a>1.stream vs channel</h3><ul>
<li>stream 不会自动缓冲数据，channel 会利用系统提供的发送缓冲区、接收缓冲区（更为底层）</li>
<li>stream 仅支持阻塞 API，channel 同时支持阻塞、非阻塞 API，网络 channel 可配合 selector 实现多路复用</li>
<li>二者均为全双工，即读写可以同时进行</li>
</ul>
<h3 id="2-IO模型"><a href="#2-IO模型" class="headerlink" title="2.IO模型"></a>2.IO模型</h3><p>同步阻塞、同步非阻塞、同步多路复用、异步阻塞（没有此情况）、异步非阻塞</p>
<ul>
<li>同步：线程自己去获取结果（一个线程）<ul>
<li>例如：线程调用一个方法后，需要等待方法返回结果</li>
</ul>
</li>
<li>异步：线程自己不去获取结果，而是由其它线程送结果（至少两个线程）<ul>
<li>例如：线程A调用一个方法后，继续向下运行，运行结果由线程B返回</li>
</ul>
</li>
</ul>
<p>当调用一次 channel.read 或 stream.read 后，会由用户态切换至操作系统内核态来完成真正数据读取，而读取又分为两个阶段，分别为：</p>
<ul>
<li>等待数据阶段</li>
<li>复制数据阶段</li>
</ul>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210418151243.png" srcset="/blogshow/img/loading.gif" lazyload alt="20210418151243"></p>
<h4 id="阻塞IO"><a href="#阻塞IO" class="headerlink" title="阻塞IO"></a>阻塞IO</h4><p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210418151605.png" srcset="/blogshow/img/loading.gif" lazyload alt="20210418151605"></p>
<ul>
<li>用户线程进行read操作时，需要等待操作系统执行实际的read操作，此期间用户线程是被阻塞的，无法执行其他操作<h4 id="非阻塞IO"><a href="#非阻塞IO" class="headerlink" title="非阻塞IO"></a>非阻塞IO</h4></li>
</ul>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210418152137.png" srcset="/blogshow/img/loading.gif" lazyload alt="20210418152137"></p>
<ul>
<li>用户线程在一个循环中一直调用read方法，若内核空间中还没有数据可读，立即返回<ul>
<li>只是在等待阶段非阻塞，复制阶段仍是阻塞</li>
</ul>
</li>
<li>用户线程发现内核空间中有数据后，等待内核空间执行复制数据，待复制结束后返回结果<h4 id="多路复用"><a href="#多路复用" class="headerlink" title="多路复用"></a>多路复用</h4></li>
</ul>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210418154208.png" srcset="/blogshow/img/loading.gif" lazyload alt="20210418154208"></p>
<p><strong>Java中通过Selector实现多路复用</strong><br>当没有事件是，调用select方法会被阻塞住<br>一旦有一个或多个事件发生后，就会处理对应的事件，从而实现多路复用</p>
<p><strong>多路复用与阻塞IO的区别</strong><br>阻塞IO模式下，若线程因accept事件被阻塞，发生read事件后，仍需等待accept事件执行完成后，才能去处理read事件<br>多路复用模式下，一个事件发生后，若另一个事件处于阻塞状态，不会影响该事件的执行</p>
<h4 id="信号驱动"><a href="#信号驱动" class="headerlink" title="信号驱动"></a>信号驱动</h4><h4 id="异步IO"><a href="#异步IO" class="headerlink" title="异步IO"></a>异步IO</h4><ul>
<li><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210418160106.png" srcset="/blogshow/img/loading.gif" lazyload alt="20210418160106">线程1调用方法后立即返回，不会被阻塞也不需要立即获取结果</li>
<li>当方法的运行结果出来以后，由线程2将结果返回给线程1</li>
</ul>
<h3 id="3-零拷贝"><a href="#3-零拷贝" class="headerlink" title="3.零拷贝"></a>3.零拷贝</h3>
            </div>
            <hr>
            <div>
              <div class="post-metas mb-3">
                
                  <div class="post-meta mr-3">
                    <i class="iconfont icon-category"></i>
                    
                      <a class="hover-with-bg" href="/blogshow/categories/netty/">netty</a>
                    
                  </div>
                
                
                  <div class="post-meta">
                    <i class="iconfont icon-tags"></i>
                    
                      <a class="hover-with-bg" href="/blogshow/tags/java/">java</a>
                    
                  </div>
                
              </div>
              
                <p class="note note-warning">
                  
                    本博客所有文章除特别声明外，均采用 <a target="_blank" href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh" rel="nofollow noopener noopener">CC BY-SA 4.0 协议</a> ，转载请注明出处！
                  
                </p>
              
              
                <div class="post-prevnext">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/blogshow/2020/10/29/JVM%E5%AD%A6%E4%B9%A0/">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">JVM学习</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/blogshow/2020/10/22/nio/">
                        <span class="hidden-mobile">Java NIO</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
          </article>
        </div>
      </div>
    </div>
    
      <div class="d-none d-lg-block col-lg-2 toc-container" id="toc-ctn">
        <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div class="toc-body" id="toc-body"></div>
</div>

      </div>
    
  </div>
</div>

<!-- Custom -->


    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
    

    
  </main>

  <footer class="text-center mt-5 py-3">
  <div class="footer-content">
     <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a> <i class="iconfont icon-love"></i> <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a> 
  </div>
  
  <div class="statistics">
    
    

    
      
        <!-- 不蒜子统计PV -->
        <span id="busuanzi_container_site_pv" style="display: none">
            总访问量 
            <span id="busuanzi_value_site_pv"></span>
             次
          </span>
      
      
        <!-- 不蒜子统计UV -->
        <span id="busuanzi_container_site_uv" style="display: none">
            总访客数 
            <span id="busuanzi_value_site_uv"></span>
             人
          </span>
      
    
  </div>


  

  
</footer>


  <!-- SCRIPTS -->
  
  <script  src="https://cdn.jsdelivr.net/npm/nprogress@0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nprogress@0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js" ></script>
<script  src="https://cdn.jsdelivr.net/npm/bootstrap@4/dist/js/bootstrap.min.js" ></script>
<script  src="/blogshow/js/events.js" ></script>
<script  src="/blogshow/js/plugins.js" ></script>

<!-- Plugins -->


  <script  src="/blogshow/js/local-search.js" ></script>



  
    <script  src="/blogshow/js/img-lazyload.js" ></script>
  



  



  
    <script  src="https://cdn.jsdelivr.net/npm/tocbot@4/dist/tocbot.min.js" ></script>
  
  
    <script  src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3/dist/jquery.fancybox.min.js" ></script>
  
  
    <script  src="https://cdn.jsdelivr.net/npm/anchor-js@4/anchor.min.js" ></script>
  
  
    <script defer src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js" ></script>
  



  <script defer src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ></script>




  <script  src="https://cdn.jsdelivr.net/npm/typed.js@2/lib/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var title = document.getElementById('subtitle').title;
      
      typing(title)
      
    })(window, document);
  </script>















<!-- 主题的启动项 保持在最底部 -->
<script  src="/blogshow/js/boot.js" ></script>


</body>
</html>
